Исследования рынка общепита в Москве для принятия решения об открытии нового заведения¶

Инвесторы из фонда «Shut Up and Take My Money» решили попробовать себя в новой области и открыть заведение общественного питания в Москве. Заказчики ещё не знают, что это будет за место: кафе, ресторан, пиццерия, паб или бар, — и какими будут расположение, меню и цены.

Для начала они просят подготовить исследование рынка общественного питания Москвы, найти интересные особенности и презентовать полученные результаты, которые в будущем помогут в выборе подходящего инвесторам места.

Подготовим информативную и лаконичную презентацию. Мне доступен датасет с заведениями общественного питания Москвы, составленный на основе данных сервисов Яндекс Карты и Яндекс Бизнес на лето 2022 года.

Описание данных¶

Файл: /datasets/moscow_places.csv

  • name — название заведения;
  • address — адрес заведения;
  • category — категория заведения, например «кафе», «пиццерия» или «кофейня»;
  • hours — информация о днях и часах работы;
  • lat — широта географической точки, в которой находится заведение;
  • lng — долгота географической точки, в которой находится заведение;
  • rating — рейтинг заведения по оценкам пользователей в Яндекс Картах (высшая оценка — 5.0);
  • price — категория цен в заведении, например «средние», «ниже среднего», «выше среднего» и так далее;
  • avg_bill — строка, которая хранит среднюю стоимость заказа в виде диапазона, например:

    • «Средний счёт: 1000–1500 ₽»;
    • «Цена чашки капучино: 130–220 ₽»;
    • «Цена бокала пива: 400–600 ₽». и так далее;*
  • middle_avg_bill — число с оценкой среднего чека, которое указано только для значений из столбца avg_bill, начинающихся с
    подстроки «Средний счёт»: Если в строке указан ценовой диапазон из двух значений, в столбец войдёт медиана этих двух значений.
    Если в строке указано одно число — цена без диапазона, то в столбец войдёт это число. Если значения нет или оно не начинается с подстроки «Средний счёт», то в столбец ничего не войдёт.

  • middle_coffee_cup — число с оценкой одной чашки капучино, которое указано только для значений из столбца avg_bill, начинающихся с подстроки «Цена одной чашки капучино»: Если в строке указан ценовой диапазон из двух значений, в столбец войдёт медиана этих двух значений. Если в строке указано одно число — цена без диапазона, то в столбец войдёт это число. Если значения нет или оно не начинается с подстроки «Цена одной чашки капучино», то в столбец ничего не войдёт.

  • chain — число, выраженное 0 или 1, которое показывает, является ли заведение сетевым (для маленьких сетей могут
    встречаться ошибки):

    • 0 — заведение не является сетевым
    • 1 — заведение является сетевым
  • district — административный район, в котором находится заведение, например Центральный административный округ;

  • seats — количество посадочных мест.

План проекта¶

Шаг 1. Загрузка данных и изучение информации

  • Путь к файлу: /datasets/moscow_places.csv
  • Сколько заведений представлено?
  • Что можно сказать о каждом столбце?
  • Значения какого типа они хранят?

Шаг 2. Выполнение предобработки данных

  • Есть ли дубликаты в данных.
  • Есть ли пропуски: встречаются ли они, в каких столбцах? Можно ли их обработать или оставить как есть?

Предобработка данных:

  • Создать столбец street с названиями улиц из столбца с адресом.
  • Создать столбец is_24/7 с обозначением, что заведение работает ежедневно и круглосуточно (24/7): логическое значение True — если заведение работает ежедневно и круглосуточно; логическое значение False — в противоположном случае.

Шаг 3. Анализ данных

  • Какие категории заведений представлены в данных?
  • Исследование количества объектов общественного питания по категориям: рестораны, кофейни, пиццерии, бары и так далее.
    Построение визуализации. Распределении заведений по категориям.
  • Исследование количества посадочных мест в местах по категориям: рестораны, кофейни, пиццерии, бары и так далее. Построение
    визуализации. Анализ результатов, выводы.
  • Необходимо рассмотреть и изобразить соотношение сетевых и несетевых заведений в датасете. Каких заведений больше?
  • Какие категории заведений чаще являются сетевыми? Исследование данных и ответ на вопрос графиком.
  • Сгруппировать данные по названиям заведений и найти топ-15 популярных сетей в Москве. Под популярностью понимается количество заведений этой сети в регионе. Построение визуализации. Знакомы ли вам эти сети? Есть ли какой-то признак, который их объединяет? К какой категории заведений они относятся?
  • Какие административные районы Москвы присутствуют в датасете? Отобразить общее количество заведений и количество заведений каждой категории по районам. Проиллюстрировать эту информацию одним графиком.
  • Визуализировать распределение средних рейтингов по категориям заведений. Сильно ли различаются усреднённые рейтинги в разных типах общепита?
  • Построить фоновую картограмму (хороплет) со средним рейтингом заведений каждого района. Границы районов Москвы, которые встречаются в датасете, хранятся в файле admin_level_geomap.geojson.
  • Отобразить все заведения датасета на карте с помощью кластеров средствами библиотеки folium.
  • Найти топ-15 улиц по количеству заведений. Построить график распределения количества заведений и их категорий по этим улицам. Проиллюстрировать эту информацию одним графиком.
  • Найти улицы, на которых находится только один объект общепита. Что можно сказать об этих заведениях?
  • Значения средних чеков заведений хранятся в столбце middle_avg_bill. Эти числа показывают примерную стоимость заказа в рублях, которая чаще всего выражена диапазоном. Посчитайте медиану этого столбца для каждого района. Используйте это значение в качестве ценового индикатора района. Постройте фоновую картограмму (хороплет) с полученными значениями для каждого района. Проанализируйте цены в центральном административном округе и других. Как удалённость от центра влияет на цены в заведениях?
  • Соберите наблюдения по вопросам выше в один общий вывод.

Шаг 4. Детализация исследования: открытие кофейни

Основателям фонда «Shut Up and Take My Money» не даёт покоя успех сериала «Друзья». Их мечта — открыть такую же крутую и доступную, как «Central Perk», кофейню в Москве. Будем считать, что заказчики не боятся конкуренции в этой сфере, ведь кофеен в больших городах уже достаточно. Попробуйте определить, осуществима ли мечта клиентов.

Необходимо ответить на вопросы:

  • Сколько всего кофеен в датасете? В каких районах их больше всего, каковы особенности их расположения?
  • Есть ли круглосуточные кофейни?
  • Какие у кофеен рейтинги? Как они распределяются по районам?
  • На какую стоимость чашки капучино стоит ориентироваться при открытии и почему?
  • По желанию вы можете расширить список вопросов для исследования, добавив собственные.

Постройте визуализации. Попробуйте дать рекомендацию для открытия нового заведения. Это творческое задание: здесь нет правильного или неправильного ответа, но ваше решение должно быть чем-то обосновано. Объяснить свою рекомендацию можно текстом с описанием или маркерами на географической карте.

Шаг 5. Подготовка презентации

Подготовьте презентацию исследования для инвесторов. Отвечая на вопросы о московском общепите, вы уже построили много диаграмм, и помещать каждую из них в презентацию не нужно. Выберите важные тезисы и наблюдения, которые могут заинтересовать заказчиков. Для создания презентации используйте любой удобный инструмент, но отправить презентацию нужно обязательно в формате PDF. Приложите ссылку на презентацию в markdown-ячейке в формате: Презентация: <ссылка на облачное хранилище с презентацией>

Импорт библитек¶

In [1]:
# импортируем необходимые для работы библиотеки

import pandas as pd 
import numpy as np
import numpy as npмsns 
import datetime as dt
import seaborn as sns
pd.set_option('display.max_columns', 0)
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objects as go
import warnings
warnings.filterwarnings("ignore")

# импортируем карту и хороплет

from folium import Map, Choropleth, Marker

# импортируем кластер

from folium.plugins import MarkerCluster
In [2]:
import matplotlib
%config InlineBackend.figure_format = 'retina' # используем для более красивых графичков. 
matplotlib.style.use('ggplot')
plt.rc('figure', figsize=(15, 6))

Загрузка данных и изучение информации; Предобработка данных¶

In [3]:
# для исключения ошибок загрузки используем функцию try и expert

try:
    df = pd.read_csv('/datasets/moscow_places.csv')
except:
    df = pd.read_csv('https://code.s3.yandex.net/datasets/moscow_places.csv')
In [4]:
# напишем функцию для обзора данных

def df_review(df):
    display(df.head())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.info())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.describe())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.isna().sum())
    print('---------------------------------------------------------------------------------------------------------')
    print(df.duplicated().sum())
In [6]:
df_review(df)
name category address district hours lat lng rating price avg_bill middle_avg_bill middle_coffee_cup chain seats
0 WoWфли кафе Москва, улица Дыбенко, 7/1 Северный административный округ ежедневно, 10:00–22:00 55.878494 37.478860 5.0 NaN NaN NaN NaN 0 NaN
1 Четыре комнаты ресторан Москва, улица Дыбенко, 36, корп. 1 Северный административный округ ежедневно, 10:00–22:00 55.875801 37.484479 4.5 выше среднего Средний счёт:1500–1600 ₽ 1550.0 NaN 0 4.0
2 Хазри кафе Москва, Клязьминская улица, 15 Северный административный округ пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... 55.889146 37.525901 4.6 средние Средний счёт:от 1000 ₽ 1000.0 NaN 0 45.0
3 Dormouse Coffee Shop кофейня Москва, улица Маршала Федоренко, 12 Северный административный округ ежедневно, 09:00–22:00 55.881608 37.488860 5.0 NaN Цена чашки капучино:155–185 ₽ NaN 170.0 0 NaN
4 Иль Марко пиццерия Москва, Правобережная улица, 1Б Северный административный округ ежедневно, 10:00–22:00 55.881166 37.449357 5.0 средние Средний счёт:400–600 ₽ 500.0 NaN 1 148.0
---------------------------------------------------------------------------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8406 entries, 0 to 8405
Data columns (total 14 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   name               8406 non-null   object 
 1   category           8406 non-null   object 
 2   address            8406 non-null   object 
 3   district           8406 non-null   object 
 4   hours              7870 non-null   object 
 5   lat                8406 non-null   float64
 6   lng                8406 non-null   float64
 7   rating             8406 non-null   float64
 8   price              3315 non-null   object 
 9   avg_bill           3816 non-null   object 
 10  middle_avg_bill    3149 non-null   float64
 11  middle_coffee_cup  535 non-null    float64
 12  chain              8406 non-null   int64  
 13  seats              4795 non-null   float64
dtypes: float64(6), int64(1), object(7)
memory usage: 919.5+ KB
None
---------------------------------------------------------------------------------------------------------
               lat          lng  ...        chain        seats
count  8406.000000  8406.000000  ...  8406.000000  4795.000000
mean     55.750109    37.608570  ...     0.381275   108.421689
std       0.069658     0.098597  ...     0.485729   122.833396
min      55.573942    37.355651  ...     0.000000     0.000000
25%      55.705155    37.538583  ...     0.000000    40.000000
50%      55.753425    37.605246  ...     0.000000    75.000000
75%      55.795041    37.664792  ...     1.000000   140.000000
max      55.928943    37.874466  ...     1.000000  1288.000000

[8 rows x 7 columns]
---------------------------------------------------------------------------------------------------------
name                    0
category                0
address                 0
district                0
hours                 536
lat                     0
lng                     0
rating                  0
price                5091
avg_bill             4590
middle_avg_bill      5257
middle_coffee_cup    7871
chain                   0
seats                3611
dtype: int64
---------------------------------------------------------------------------------------------------------
0
In [5]:
# приведем столбец к нижнему регистру

df['name'] = df['name'].str.lower()
df['address'] = df['address'].str.lower()
df.head()
Out[5]:
name category address district hours lat lng rating price avg_bill middle_avg_bill middle_coffee_cup chain seats
0 wowфли кафе москва, улица дыбенко, 7/1 Северный административный округ ежедневно, 10:00–22:00 55.878494 37.478860 5.0 NaN NaN NaN NaN 0 NaN
1 четыре комнаты ресторан москва, улица дыбенко, 36, корп. 1 Северный административный округ ежедневно, 10:00–22:00 55.875801 37.484479 4.5 выше среднего Средний счёт:1500–1600 ₽ 1550.0 NaN 0 4.0
2 хазри кафе москва, клязьминская улица, 15 Северный административный округ пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... 55.889146 37.525901 4.6 средние Средний счёт:от 1000 ₽ 1000.0 NaN 0 45.0
3 dormouse coffee shop кофейня москва, улица маршала федоренко, 12 Северный административный округ ежедневно, 09:00–22:00 55.881608 37.488860 5.0 NaN Цена чашки капучино:155–185 ₽ NaN 170.0 0 NaN
4 иль марко пиццерия москва, правобережная улица, 1б Северный административный округ ежедневно, 10:00–22:00 55.881166 37.449357 5.0 средние Средний счёт:400–600 ₽ 500.0 NaN 1 148.0
In [8]:
df.duplicated(subset=['name', 'address']).sum()
print(len(df))
df = df.drop_duplicates(subset=['name', 'address'])
print(len(df))
8406
8402

Удалили неявные дубликаты.

In [9]:
print('Количество заведений:', df['name'].nunique())
Количество заведений: 5512
In [10]:
df['category'].value_counts()
Out[10]:
category
кафе               2376
ресторан           2042
кофейня            1413
бар,паб             764
пиццерия            633
быстрое питание     603
столовая            315
булочная            256
Name: count, dtype: int64

Вывод:

  • В исходных данных 8406 строк в таблице, 5512 заведений, тип данных соответствует значениям, изменять название столбцов не
    требуется.
  • Видим пропуски в столбцах:

    • hours,
    • price,
    • avg_bill,
    • middle_avg_bill,
    • middle_coffee_cup,
    • seats.

    Пропусков в данных большое количество. Просто удалить их нельзя, т. к. будут искажения в исследовании. Исследуем эти значения и посмотрим чем их можно заменить.

Для дальнейшего исследования нам нужен столбец middle_avg_bill. Значения для этого столбца берутся из avg_bill.

In [11]:
new_avg_bill = df.groupby('avg_bill')['middle_avg_bill'].max().dropna()
display(new_avg_bill.index)
display(new_avg_bill.head())
Index(['Средний счёт:100 ₽', 'Средний счёт:1000 ₽',
       'Средний счёт:1000–10000 ₽', 'Средний счёт:1000–1100 ₽',
       'Средний счёт:1000–1200 ₽', 'Средний счёт:1000–1300 ₽',
       'Средний счёт:1000–1350 ₽', 'Средний счёт:1000–1400 ₽',
       'Средний счёт:1000–1500 ₽', 'Средний счёт:1000–1600 ₽',
       ...
       'Средний счёт:от 499 ₽', 'Средний счёт:от 50 ₽',
       'Средний счёт:от 500 ₽', 'Средний счёт:от 5000 ₽',
       'Средний счёт:от 549 ₽', 'Средний счёт:от 600 ₽',
       'Средний счёт:от 650 ₽', 'Средний счёт:от 700 ₽',
       'Средний счёт:от 800 ₽', 'Средний счёт:от 850 ₽'],
      dtype='object', name='avg_bill', length=555)
avg_bill
Средний счёт:100 ₽            100.0
Средний счёт:1000 ₽          1000.0
Средний счёт:1000–10000 ₽    5500.0
Средний счёт:1000–1100 ₽     1050.0
Средний счёт:1000–1200 ₽     1100.0
Name: middle_avg_bill, dtype: float64

Отобрали все значения начинающиеся с "Средний счёт:".

Вывод:

Полагаю, что заполнять средними или медианными значениями по названию заведения остальные столбцы некорректно, так как количество мест в сетевых заведениях, в разных районах, может отличаться. Также в мелких сетях, по условию, могут быть ошибки. Оставим пропуски как есть.

Посмотрим на количество сидячих мест

In [15]:
df['seats'].describe()
Out[15]:
count    4792.000000
mean      108.361436
std       122.841130
min         0.000000
25%        40.000000
50%        75.000000
75%       140.000000
max      1288.000000
Name: seats, dtype: float64

1288 - очень большое значение, даже для Москвы. Построим boxplot и посмотрим на выбросы.

In [16]:
# построим график boxplot для визуализации выборосов

sns.set_palette('rocket')
sns.boxplot(y='seats', x='category', data=df, hue="category", dodge=False)
sns.set(rc={'figure.figsize':(13,13)})
plt.title('Количество сидячих мест в заведениях')
plt.xlabel('Тип заведения')
plt.ylabel('Количество сидячих мест')
plt.show()

Вывод:

На графике есть выбросы. Скорее всего они связаны с ошибкой при заполнении данных. Установим допустимую границу в 560 мест. Остальное отсечем. Посчитаем количество таких заведений.

In [6]:
print('Количество заведений с числом мест больше 560:', df[df['seats'] > 560]['name'].count())
Количество заведений с числом мест больше 560: 56

Таких заведений меньше 1%, можно их удалить.

In [7]:
df_550 = df[df['seats'] > 560]
df = df.query('index not in @df_550.index')
print('Заведений с числом мест больше 560:', df[df['seats'] > 560]['name'].count())
Заведений с числом мест больше 560: 0

Предобработка данных:

Создадим столбец street с названиями улиц из столбца address

In [8]:
df['street'] = df['address'].str.split(', ').str[1]
In [9]:
df.head()
Out[9]:
name category address district hours lat lng rating price avg_bill middle_avg_bill middle_coffee_cup chain seats street
0 wowфли кафе москва, улица дыбенко, 7/1 Северный административный округ ежедневно, 10:00–22:00 55.878494 37.478860 5.0 NaN NaN NaN NaN 0 NaN улица дыбенко
1 четыре комнаты ресторан москва, улица дыбенко, 36, корп. 1 Северный административный округ ежедневно, 10:00–22:00 55.875801 37.484479 4.5 выше среднего Средний счёт:1500–1600 ₽ 1550.0 NaN 0 4.0 улица дыбенко
2 хазри кафе москва, клязьминская улица, 15 Северный административный округ пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... 55.889146 37.525901 4.6 средние Средний счёт:от 1000 ₽ 1000.0 NaN 0 45.0 клязьминская улица
3 dormouse coffee shop кофейня москва, улица маршала федоренко, 12 Северный административный округ ежедневно, 09:00–22:00 55.881608 37.488860 5.0 NaN Цена чашки капучино:155–185 ₽ NaN 170.0 0 NaN улица маршала федоренко
4 иль марко пиццерия москва, правобережная улица, 1б Северный административный округ ежедневно, 10:00–22:00 55.881166 37.449357 5.0 средние Средний счёт:400–600 ₽ 500.0 NaN 1 148.0 правобережная улица

Создадим столбец is_24/7 с обозначением, что заведение работает ежедневно и круглосуточно (24/7): логическое значение True — если заведение работает ежедневно и круглосуточно; логическое значение False — в противоположном случае.

In [10]:
# добавим столбец и выведем результаты на экран

df['is_24/7'] = df['hours'].str.contains('ежедневно, круглосуточно')
df.head()
Out[10]:
name category address district hours lat lng rating price avg_bill middle_avg_bill middle_coffee_cup chain seats street is_24/7
0 wowфли кафе москва, улица дыбенко, 7/1 Северный административный округ ежедневно, 10:00–22:00 55.878494 37.478860 5.0 NaN NaN NaN NaN 0 NaN улица дыбенко False
1 четыре комнаты ресторан москва, улица дыбенко, 36, корп. 1 Северный административный округ ежедневно, 10:00–22:00 55.875801 37.484479 4.5 выше среднего Средний счёт:1500–1600 ₽ 1550.0 NaN 0 4.0 улица дыбенко False
2 хазри кафе москва, клязьминская улица, 15 Северный административный округ пн-чт 11:00–02:00; пт,сб 11:00–05:00; вс 11:00... 55.889146 37.525901 4.6 средние Средний счёт:от 1000 ₽ 1000.0 NaN 0 45.0 клязьминская улица False
3 dormouse coffee shop кофейня москва, улица маршала федоренко, 12 Северный административный округ ежедневно, 09:00–22:00 55.881608 37.488860 5.0 NaN Цена чашки капучино:155–185 ₽ NaN 170.0 0 NaN улица маршала федоренко False
4 иль марко пиццерия москва, правобережная улица, 1б Северный административный округ ежедневно, 10:00–22:00 55.881166 37.449357 5.0 средние Средний счёт:400–600 ₽ 500.0 NaN 1 148.0 правобережная улица False

Анализ данных¶

Категории заведений

Посмотрим какие категории заведений представлены в данных. Исследуем количество объектов общественного питания по категориям:

  • рестораны,
  • кофейни,
  • пиццерии,
  • бары,
  • и так далее.

Построим визуализации.

In [11]:
# расчитаем количество заведений по категориям

category_name = df.groupby('category')['name'].count().reset_index()
category_name.columns = ['category', 'count']
category_name
Out[11]:
category count
0 бар,паб 755
1 булочная 255
2 быстрое питание 602
3 кафе 2364
4 кофейня 1401
5 пиццерия 629
6 ресторан 2031
7 столовая 313
In [13]:
# построим круговую диаграмму для распределения заведений по категориям в процентах

go.Figure(data=[go.Pie(labels=category_name['category'], 
                       values= category_name['count'])], 
                       layout = go.Layout(title=go.layout.Title(text='Категории заведений общественного питания г. Масква')))
In [14]:
# построим график для отображение количества заведений по категориям

fig = px.bar(category_name,
             x='category',
             y='count',
             text = 'count',
             #color = 'count',
             title = 'Количество заведений общественного питания по категориям'
             )
fig.update_layout(xaxis_title = 'Категории заведений',
                  yaxis_title = 'Количество заведений',
                  xaxis={'categoryorder':'total descending'})
fig.show()

Вывод:

По количеству заведений лидируют кафе с численностью 2362 заведений., немного меньше ресторанов - 2030 шт. На последнем месте булочная - 255 шт.

Кафе и рестораны составляют больше половины от всех заведений. Кофейни, бары/пабы и пиццерии составляют 1/3 от общего числа заведений. На булочные, столовые и быстрое питание приходится около 14%.

Исследование количества посадочных мест в местах по категориям.

Исследуем количество посадочных мест в местах по категориям: рестораны, кофейни, пиццерии, бары и так далее. Построим визуализации.

In [15]:
# выведем общую информацию count, mean, std, min, max о заведениях 

df.groupby('category')['seats'].describe().round(2).reset_index()
Out[15]:
category count mean std min 25% 50% 75% max
0 бар,паб 458.0 109.04 93.20 0.0 47.25 80.0 140.00 480.0
1 булочная 147.0 85.74 87.35 0.0 25.00 50.0 120.00 478.0
2 быстрое питание 348.0 96.19 94.02 0.0 27.25 65.0 136.25 500.0
3 кафе 1204.0 89.57 88.93 0.0 35.00 60.0 106.00 500.0
4 кофейня 739.0 100.43 90.99 0.0 40.00 78.0 140.00 500.0
5 пиццерия 423.0 87.91 85.48 0.0 30.00 52.0 120.00 455.0
6 ресторан 1258.0 114.96 96.94 0.0 48.00 86.0 150.00 500.0
7 столовая 162.0 89.72 77.27 0.0 40.00 75.0 115.00 428.0
In [16]:
# построим график для отображения среднего количества мест в заведениях питания

seats_mean = df[df['seats'] !=0].groupby('category')['seats'].mean().reset_index().sort_values(by='seats', ascending=False)

f, ax = plt.subplots(figsize=(16, 7))

ax = sns.barplot(data = seats_mean, x = 'category', y = 'seats')
ax.set_xlabel('Тип заведения')
ax.set_ylabel('Количество мест')
ax.set_title('Среднее количество мест в заведениях в Москве')
plt.xticks(rotation=45)
plt.show()
In [17]:
# визуализируем медианное количество мест в заведениях

seats_median = df[df['seats'] != 0].groupby('category')['seats'].median().reset_index().sort_values(by='seats', ascending=False).round(2)

f, ax = plt.subplots(figsize=(16, 7))

ax = sns.barplot(data=seats_median, x='category', y='seats')

ax.set_xlabel('Тип заведения')
ax.set_ylabel('Количество мест')
ax.set_title('Медианное количество мест в заведениях Москвы')
plt.xticks(rotation=45)
plt.show()
In [19]:
order = df.groupby(["category"])["seats"].median().sort_values(ascending=False).index
In [20]:
# построим график для распределения количества мест по типам заведений

plt.figure(figsize=(15, 7))
ax = sns.boxplot(data=df, x='seats', y='category', palette="Set2", order = order)
plt.title('Распределение количества посадочных мест по типам заведений')
plt.xlabel('Количество мест')
plt.ylabel('Тип заведений')
plt.show()

Вывод:

Больше всего посадочных мест в ресторанах, что логично. Люди празднуют свадьбы, проводят корпоративы и т. д. Далее идут заведения быстрого питания, т. к. они больше всего распространены в Москве. Кафе и столовые располагают для своих гостей меньшим количеством мест.

Cоотношение сетевых и несетевых заведений в датасете

In [22]:
chain = df['chain'].value_counts().reset_index()
chain.columns = ['chain', 'count']
chain
Out[22]:
chain count
0 0 5171
1 1 3179
In [23]:
# визуализируем результаты на диаграмме

fig = go.Figure(data=[go.Pie(labels=['несетевые', 'сетевые'], values=chain['count'])], layout=go.Layout(
        title=go.layout.Title(text="Соотношение сетевых и несетевых заведений")))
fig.show()

Вывод:

По графику видно, что почти 62% заведений в Москве - несетевые.

Посмотрим какие категории заведений чаще являются сетевыми

In [24]:
# посчитаем рапсределение заведение по типу сетевые и нет

chain_establishments = df.groupby(['category', 'chain'])['name'].count().reset_index()
chain_establishments.columns = ['type', 'chain', 'count']
chain_establishments['chain'] = chain_establishments['chain'].astype(object)
chain_establishments = chain_establishments.sort_values(['count', 'chain'])
chain_establishments
Out[24]:
type chain count
15 столовая 1 87
2 булочная 0 99
3 булочная 1 156
1 бар,паб 1 168
14 столовая 0 226
5 быстрое питание 1 231
10 пиццерия 0 301
11 пиццерия 1 328
4 быстрое питание 0 371
0 бар,паб 0 587
8 кофейня 0 688
9 кофейня 1 713
13 ресторан 1 722
7 кафе 1 774
12 ресторан 0 1309
6 кафе 0 1590
In [25]:
# построим график для визуализации расчетов

fig = px.bar(chain_establishments,
             x='count',
             y='type',
             text= 'count',
             color='chain',
             category_orders={"chain":['Сетевой', 'Несетевой']},
             color_discrete_sequence=["#E69F00", "#56B4E9"],
             height=500,
             width=700
             )
fig.update_layout(title = 'Соотношение сетевых и несетевых заведений',
                  xaxis_title = 'Количество заведений',
                  yaxis_title = 'Категории')
fig.show()
In [26]:
chain_grouped = df[df['chain'] == 1].groupby(['category'])['name'].count()\
                                 .reset_index().sort_values(by='name', ascending=False)
notChain_grouped = df[df['chain'] == 0].groupby(['category'])['name'].count()\
                                 .reset_index().sort_values(by='name', ascending=False)

# инициализация фигуры
fig = go.Figure()
# добавление барчарта методом Bar()
fig.add_trace(go.Bar(x=chain_grouped.category, y=chain_grouped.name,\
                     name='Сетевое', marker={'color': '#E69F00', 'opacity': 0.7}))
fig.add_trace(go.Bar(x=notChain_grouped.category, y=notChain_grouped.name,\
                     name='Несетевое', marker={'color': '#56B4E9', 'opacity': 0.7}))
# добавление соответствующих подписей и меток
fig.update_layout(title='Соотношение сетевых и несетевых заведений по количеству',\
                  xaxis_title='Категория', yaxis_title='Количество объектов', legend=dict(y=0.95, x=0.84))
fig.update_traces(hoverinfo='all', hovertemplate='Количество объектов: %{y}')
# отображение фигуры
fig.show()

Вывод:

По графику видно, что большинство за несетевыми заведениями. Но есть пара исключений:

  • кофейня - количество сетевых немного больше. 713/688
  • пиццерия - сетевых заведений больше. 328/301
  • булочная - сетевых заметно больше. 156/99

Топ-15 популярных сетей в Москве

In [27]:
# сгруппируем данные по названиям заведений и найдем топ-15 популярных сетей в Москве

data_chain = df[df['chain'] == 1]
top_15 = data_chain.groupby('name').agg({'rating': 'median', 'category' : pd.Series.mode, 'district' : 'count'})
top_15 = top_15.rename(columns={'district':'count'})
top_15 = top_15.sort_values('count', ascending = False).reset_index().head(15)
top_15
Out[27]:
name rating category count
0 шоколадница 4.20 кофейня 118
1 домино'с пицца 4.20 пиццерия 76
2 додо пицца 4.30 пиццерия 74
3 one price coffee 4.20 кофейня 70
4 яндекс лавка 4.00 ресторан 68
5 cofix 4.10 кофейня 65
6 prime 4.20 ресторан 50
7 хинкальная 4.40 кафе 44
8 кофепорт 4.20 кофейня 42
9 кулинарная лавка братьев караваевых 4.40 кафе 38
10 теремок 4.10 ресторан 38
11 чайхана 4.10 кафе 37
12 cofefest 4.05 кофейня 32
13 буханка 4.40 булочная 32
14 му-му 4.30 кафе 27
In [28]:
# построим график для визуализации произведеннйх расчетов

fig = px.bar(top_15,
             x='count',
             y='name',
             text='count',
             color='name',
             height=500,
             width=1000
             )
fig.update_layout(title = 'Топ-15 популярных сетей Москвы',
                  xaxis_title = 'Количество заведений',
                  yaxis_title = 'Название заведений',
                  showlegend = False)
fig.show()

Вывод:

Из графика видно, что самая популярная сеть - Шоколадница. Далее идут заведения продающих пиццу. Сеть Му-Му замыкает топ.

In [29]:
print('Всего заведений в Топ-15:', top_15['count'].sum())
Всего заведений в Топ-15: 811
In [38]:
fig = px.bar(top_15,
             x='count', 
             y='category',
             color='category',
             text = 'name'
            )

fig.update_layout(title='Количество заведений по категориям из топ-15 популярных сетей Москвы',
                   xaxis_title='Количество заведений',
                   yaxis_title='Название категорий',
                   yaxis={'categoryorder':'total ascending'},
                   width=900,
                   height=500
                 )
fig.show()
In [30]:
# расчитаем рейтинг заведений в зависимости от района 

district_chain = data_chain.groupby(['district', 'category', 'name']).agg({'rating' : 'median', 'address' : 'count'})
district_chain = district_chain.sort_values('address', ascending = False).reset_index()
district_chain = district_chain.rename(columns={'address':'count'})
district_chain = district_chain[district_chain['name'].isin(top_15['name'])]
district_chain.head()
Out[30]:
district category name rating count
0 Центральный административный округ кофейня шоколадница 4.20 38
1 Центральный административный округ ресторан prime 4.20 36
2 Центральный административный округ кафе кулинарная лавка братьев караваевых 4.40 32
3 Центральный административный округ кофейня cofix 4.20 20
4 Западный административный округ кофейня шоколадница 4.15 16
In [31]:
# построим график для вызуализации проведенных расчетов

fig = px.bar(district_chain, 
             x='count', 
             y='district',                  
             color='category'
            )

fig.update_layout(title='Количество заведений каждой категории по районам',
                   xaxis_title='Количество заведений',
                   yaxis_title='Название района',
                   yaxis={'categoryorder':'total ascending'},
                   width=1000,
                   height=700
                 )
fig.show()
In [34]:
# шаг 1 выделим датасет только с популярными заведениями

top15_count = df[df['name'].isin(top_15['name'])].groupby(['district']).agg({'name' : 'count'}).reset_index()
top15_count = top15_count.rename(columns={'name':'count'})
top15_count
Out[34]:
district count
0 Восточный административный округ 80
1 Западный административный округ 85
2 Северный административный округ 87
3 Северо-Восточный административный округ 85
4 Северо-Западный административный округ 46
5 Центральный административный округ 215
6 Юго-Восточный административный округ 56
7 Юго-Западный административный округ 70
8 Южный административный округ 89
In [35]:
# шаг 2 подсчитаем общее количество заведений по округам

district_count = df.groupby(['district']).agg({'name' : 'count'})
district_count = district_count.sort_values('name', ascending = False).reset_index()
district_count = district_count.rename(columns={'name':'total'})
district_count.head(15)
Out[35]:
district total
0 Центральный административный округ 2242
1 Северо-Восточный административный округ 891
2 Южный административный округ 891
3 Северный административный округ 877
4 Западный административный округ 831
5 Восточный административный округ 789
6 Юго-Восточный административный округ 714
7 Юго-Западный административный округ 706
8 Северо-Западный административный округ 409
In [36]:
# объединим в одну таблицу

district_merge = top15_count.merge(district_count, how='left', on='district')
district_merge['percent'] = round(district_merge['count']/district_merge['total']*100, 2)
district_merge = district_merge.sort_values('percent', ascending=False)
district_merge
Out[36]:
district count total percent
4 Северо-Западный административный округ 46 409 11.25
1 Западный административный округ 85 831 10.23
0 Восточный административный округ 80 789 10.14
8 Южный административный округ 89 891 9.99
2 Северный административный округ 87 877 9.92
7 Юго-Западный административный округ 70 706 9.92
5 Центральный административный округ 215 2242 9.59
3 Северо-Восточный административный округ 85 891 9.54
6 Юго-Восточный административный округ 56 714 7.84
In [38]:
# построи грайик для наглядности

f, ax = plt.subplots(figsize=(16, 7))
ax = sns.barplot(data=district_merge, y="district", x="percent")
ax.set_xlabel('Процент популярных заведений')
ax.set_ylabel('Название района')
ax.set_title('Процент популярных заведений от района')
plt.show()

Вывод:

Всего заведений в топ-15 - 811 шт. По графику наглядно видно, что большая часть приходится на кофейни. У ресторанов, пиццерий и кафе примерно равное количество.

Из топ-15 больше всего заведений находятся в Центральном админинстративном округе. В основном это кафе, кофейни и рестораны. Примерно одинаковые показатели у Южного, Северного, Северо-Восточного и Западного админинстративных округов. Здесь приоритеты меняются в сторону категории быстрого питания. Меньше всего заведений из топ-15 в Северо-Западном админинстративном округе.

Административные районы Москвы

Посмотрим какие административные районы Москвы присутствуют в датасете. Отобразим общее количество заведений и количество заведений каждой категории по районам.

In [39]:
print('Общее количество заведений в датасете:', df['name'].count())
Общее количество заведений в датасете: 8350
In [46]:
# расчитаем распределение заведений по округам

district_chain_data = df.groupby(['district', 'category']).agg({'rating': 'median', 'name': 'count'})
district_chain_data = district_chain_data.sort_values('rating', ascending=False).reset_index()
district_chain_data = district_chain_data.rename(columns={'name': 'count'})
district_chain_data.head()
Out[46]:
district category rating count
0 Центральный административный округ бар,паб 4.50 364
1 Западный административный округ бар,паб 4.45 46
2 Северо-Западный административный округ бар,паб 4.40 23
3 Северный административный округ бар,паб 4.40 66
4 Центральный административный округ ресторан 4.40 670
In [47]:
# визуализируем расчеты

fig = px.bar(district_chain_data,
             x='count',
             y='district',
             color='category'
            )
fig.update_layout(title = 'Количество заведений каждой категории по районам Москвы',
                  xaxis_title = 'Количество заведений',
                  yaxis_title = 'Название района',
                  yaxis={'categoryorder': 'total ascending'}
)
fig.show()
In [40]:
# выведем уникальные названия административных районов Москвы

print(f'Административные районы Москвы в датасете:', district_chain['district'].unique())
Административные районы Москвы в датасете: ['Центральный административный округ' 'Западный административный округ'
 'Северный административный округ' 'Восточный административный округ'
 'Южный административный округ' 'Северо-Восточный административный округ'
 'Юго-Западный административный округ'
 'Юго-Восточный административный округ'
 'Северо-Западный административный округ']

Вывод:

Количество районов - 9. По графику видно, что больше всего заведений располагается в Центральном административном округе. В нем преобладают кафе, кофейни и рестораны. Кафе распределены равномерно по всем округам. Столовых меньше всего по всем округам.

Средние рейтинги по категориям¶

Визуализируем распределение средних рейтингов по категориям заведений.

In [41]:
# расчитаем средний рейтинг по типам заведений

category_rating = df.groupby('category').agg({'rating': 'mean'}).round(2).sort_values('rating', ascending=False).reset_index()
category_rating
Out[41]:
category rating
0 бар,паб 4.39
1 пиццерия 4.30
2 ресторан 4.29
3 кофейня 4.28
4 булочная 4.27
5 столовая 4.21
6 кафе 4.12
7 быстрое питание 4.05
In [42]:
#  визуализируем результаты расчтеов для наглядности

fig = px.bar(category_rating,
             x='rating',
             y='category',
             text='rating',
             color='category'
            )
fig.update_layout(title='Распределение средних рейтингов по категориям заведений',
                  xaxis_title='Рейтинг',
                  yaxis_title='Название категорий')
fig.update_xaxes(range=[4, 4.5])
fig.show()

Вывод:

Из графика видно, что у категорий бар/паб наивысший рейтинг 4.39. У пиццерий, ресторанов, кофеин и булочных примерно одинаковый рейтинг. Самый маленький рейтинг у ресторанов быстрого питания. Также видно, что по все заведения имеют рейтинг выше 4, что достаточно хорошо.

Фоновая картограмма (хороплет) со средним рейтингом заведений каждого района.¶

Построим фоновую картограмму (хороплет) со средним рейтингом заведений каждого района. Границы районов Москвы, которые встречаются в датасете, хранятся в файле admin_level_geomap.geojson

In [43]:
rating_df = df.groupby('district', as_index=False)['rating'].agg('mean').round(2)
rating_df
Out[43]:
district rating
0 Восточный административный округ 4.17
1 Западный административный округ 4.18
2 Северный административный округ 4.24
3 Северо-Восточный административный округ 4.15
4 Северо-Западный административный округ 4.21
5 Центральный административный округ 4.38
6 Юго-Восточный административный округ 4.10
7 Юго-Западный административный округ 4.17
8 Южный административный округ 4.18
In [44]:
# загружаем JSON-файл с границами округов Москвы

state_geo = 'https://code.s3.yandex.net/data-analyst/admin_level_geomap.geojson'

# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы

moscow_lat, moscow_lng = 55.751244, 37.618423

# Создаем карту Мск

m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту

Choropleth(
    geo_data=state_geo,
    data=rating_df,
    columns=['district', 'rating'],
    key_on='feature.name',
    #fill_color='YlGn',
    #fill_opacity=0.8,
    legend_name='Средний рейтинг заведений по районам',
).add_to(m)

# вывод карты
m
Out[44]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

По данным видно, что самый высокий рейтинг в заведениях в Центральном админинстративном округе - 4.38. Самый низкий - в Юго-Восточном админинстратвном округе - 4.1.

Заведения датасета на карте¶

In [45]:
# moscow_lat - широта центра Москвы, moscow_lng - долгота центра Москвы

moscow_lat, moscow_lng = 55.751244, 37.618423

# создаём карту Москвы

m = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# создаём пустой кластер, добавляем его на карту

marker_cluster = MarkerCluster().add_to(m)

# функция, принимающая строку датафрейма, создает маркер в текущей точке и
# добавляет его в кластер marker_cluster

def create_cluster(row):
    Marker(
        [row['lat'], row['lng']],
        popup=f"{row['name']} {row['rating']}",
    ).add_to(marker_cluster)
    
# к каждой строке датафрейма применяем функцию

df.apply(create_cluster, axis=1)

# выводим карту
m
Out[45]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

Видно, что основная масса заведений располагаются в центре Москвы.

Топ-15 улиц по количеству заведений¶

Найдем топ-15 улиц по количеству заведений. Построим график распределения количества заведений и их категорий по этим улицам.

In [46]:
# подсчитаем распределение завдений по улицам Москвы

msc_streets = df[df['street'].notnull()]
top15_streets = msc_streets['street'].value_counts().reset_index().head(15)
top15_streets.columns = ['street', 'count']
top15_streets
Out[46]:
street count
0 проспект мира 184
1 профсоюзная улица 122
2 ленинский проспект 107
3 проспект вернадского 97
4 дмитровское шоссе 88
5 каширское шоссе 77
6 варшавское шоссе 76
7 ленинградский проспект 72
8 ленинградское шоссе 70
9 мкад 65
10 люблинская улица 60
11 улица вавилова 55
12 кутузовский проспект 53
13 пятницкая улица 48
14 алтуфьевское шоссе 47
In [48]:
# таблица со столбцами - улица - категоория - количество

streets_category = df.groupby(['street', 'category'])['name'].count().reset_index()
streets_category.columns = ['street', 'category', 'count']
streets_category.sort_values('count', ascending=False)
Out[48]:
street category count
2136 проспект мира кафе 53
2139 проспект мира ресторан 45
1519 мкад кафе 45
2137 проспект мира кофейня 36
2148 профсоюзная улица кафе 35
... ... ... ...
1534 молодёжная улица бар,паб 1
1535 молодёжная улица кофейня 1
1537 монтажная улица кофейня 1
1538 монтажная улица ресторан 1
3818 № 7 кафе 1

3819 rows × 3 columns

In [49]:
# улицы из топ-15

streets_top15_category = streets_category[streets_category['street'].isin(top15_streets['street'])]
streets_top15_category
Out[49]:
street category count
320 алтуфьевское шоссе бар,паб 5
321 алтуфьевское шоссе булочная 1
322 алтуфьевское шоссе быстрое питание 5
323 алтуфьевское шоссе кафе 13
324 алтуфьевское шоссе кофейня 9
... ... ... ...
2794 улица вавилова быстрое питание 11
2795 улица вавилова кафе 15
2796 улица вавилова кофейня 10
2797 улица вавилова пиццерия 3
2798 улица вавилова ресторан 12

113 rows × 3 columns

In [50]:
# построим график для отображения расчетов

fig = px.bar(streets_top15_category, 
             x='count', 
             y='street',                    
             color='category'
            )

fig.update_layout(title='Количество заведений каждой категории по районам',
                   xaxis_title='Количество заведений',
                   yaxis_title='Название улиц',
                   yaxis={'categoryorder':'total ascending'}
                 )
fig.show()

Вывод:

Из графика можно заметить, что больше всего заведений на проспекте Мира. Преобладают кафе и рестораны. Далее идет Профсоюзная улица. По заведениям ситуация схожая.

Найдем улицы, на которых находится только один объект общепита

In [52]:
# выполним расчеты 

streets_with_one_cafe = df['street'].value_counts().reset_index()
streets_with_one_cafe.columns = ['street', 'count_cafe']
streets_with_one_cafe = streets_with_one_cafe[streets_with_one_cafe['count_cafe']==1]
streets_with_one_cafe
Out[52]:
street count_cafe
990 андреевский пешеходный мост 1
991 загорьевский проезд 1
992 2-й верхний михайловский проезд 1
993 новороссийская улица 1
994 проезд донелайтиса 1
... ... ...
1442 улица маршала соколовского 1
1443 малый казённый переулок 1
1444 улица максимова 1
1445 басманный тупик 1
1446 чонгарский бульвар 1

457 rows × 2 columns

In [53]:
# посмотрим к каким категориям относятся заведения

streets_category_new = streets_category[streets_category['street'].isin(streets_with_one_cafe['street'])]
streets_category_new = streets_category_new.groupby('category')['street'].count().sort_values(ascending=False)
streets_category_new
Out[53]:
category
кафе               159
ресторан            93
кофейня             84
бар,паб             39
столовая            36
быстрое питание     23
пиццерия            15
булочная             8
Name: street, dtype: int64

Вывод:

Видно, что 458 улиц имеют только одно заведение. Больше всего - это кафе (160).

Значения средних чеков заведений¶

Значения средних чеков заведений хранятся в столбце middle_avg_bill. Эти числа показывают примерную стоимость заказа, которая чаще всего выражена диапазоном.

  • Посчитаем медиану этого столбца для каждого района. Используем это значение в качестве ценового индикатора района.
  • Построим фоновую картограмму (хороплет) с полученными значениями для каждого района.
In [54]:
# подсчитаем медианное значение среднего чека

median_bill = df.groupby('district')['middle_avg_bill'].median().reset_index()
median_bill
Out[54]:
district middle_avg_bill
0 Восточный административный округ 550.0
1 Западный административный округ 1000.0
2 Северный административный округ 650.0
3 Северо-Восточный административный округ 500.0
4 Северо-Западный административный округ 700.0
5 Центральный административный округ 1000.0
6 Юго-Восточный административный округ 450.0
7 Юго-Западный административный округ 600.0
8 Южный административный округ 500.0
In [55]:
# создаем карту Москвы

mm = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# хороплет

Choropleth(
    geo_data=state_geo,
    data = median_bill,
    columns = ['district', 'middle_avg_bill'],
    key_on = 'feature.name',
    legend_name = 'Средний чек заведений по районам',
).add_to(mm)

mm
Out[55]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

По данным выше мы видим, что самый высокий средний чек в Центральном и Западном округах. Самый низкий в Юго-Восточном административном округе. Из таблицы выше видно, что средний чек округов вокруг центрального в среднем отличается в 1,5 - 2 раза.

Общий вывод:¶

  • Был проведен анализ заведений общественного питания Москвы. По количеству заведений лидируют кафе с количеством 2364 (28.3%), чуть меньше рестораны - 2031 (24.3%). Категория булочные на последнем месте - 255 (3.05%).

  • В процессе проведения исседования выявлено, что больше всего посадочных мест предоставляют рестораны. Что логично, т.к. люди обычно празднуют там события с большой численностью людей (свадьбы, корпоративы и т. д.). Далее идут заведения быстрого питания, т.к. они больше всего распространены в Москве.

  • Для кафе и столовых посадочных мест меньше всего.

  • В Москве 61.9% мест являются несетевыми. По графику видно, что большинство за несетевыми заведениями. Но есть пара исключений:

    • кофейня - количество сетевых немного больше - 713/688

    • пиццерия - сетевых заведений больше - 328/301

    • булочная - сетевых заметно больше - 156/99

  • Самая популярная сеть в Москве - Шоколадница. На втором и третьем месте расположились известные пиццерии.

  • Всего заведений в топ-15 - 811. По графику наглядно видно, что большая часть приходится на кофейни. У ресторанов, пиццерий и кафе примерно равное количество.

  • Из топ-15 больше всего заведений находятся в Центральном админинстративном округе. В основном это кафе, кофейни и рестораны. Примерно одинаковые показатели у Южного, Северного, Северо-Восточного и Западного админинстративных округов. Здесь приоритеты меняются в сторону категории быстрого питания. Меньше всего заведений из топ-15 в Северо-Западном админинстративном округе.

  • Количество районов - 9. По графику видно, что больше всего заведений располагается в Центральном админинстративном округе. В нем преобладают кафе, кофейни и рестораны. Кафе распределены равномерно по всем округам. Столовых меньше всего по всем округам.

  • У категорий бар/паб наивысший рейтинг 4.39. У пиццерий, ресторанов, кофеин и булочных примерно одинаковый рейтинг. Самый маленький рейтинг у ресторанов быстрого питания. Также видно, что по все заведениям рейтинг выше 4, что достаточно хорошо.

  • Самый высокий рейтинг в заведениях в Центральном админинстративном округе - 4.38. Самый низкий - в Юго-Восточном админинстратвном округе - 4.1.

  • Основная масса заведений располагаются в центре Москвы.

  • Больше всего заведений на проспекте Мира. Преобладают кафе и рестораны. Далее идет Профсоюзная улица. По заведениям ситуация схожая.

  • 458 улиц имеют только одно заведение. Больше всего - это кафе (160).

  • Самый высокий средний чек в Центральном и Западном округах. Самый маленький в Юго-Восточном административном округе. Из таблицы выше видно, что средний чек округов вокруг центрального в среднем отличается в 1,5 - 2 раза.

Детализируем исследование: открытие кофейни¶

Ответим на следующие вопросы:

  • Сколько всего кофеен в датасете? В каких районах их больше всего, каковы особенности их расположения?
  • Есть ли круглосуточные кофейни?
  • Какие у кофеен рейтинги? Как они распределяются по районам?
  • На какую стоимость чашки капучино стоит ориентироваться при открытии и почему?

Количество кофеен¶

In [57]:
coffee_data = df[df['category'] == 'кофейня']
print('Количество кофеин:', len(coffee_data))
Количество кофеин: 1401
In [58]:
# создаем карту

mmm = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# создаем пустой кластер и добавляем его на карту

marker_cluster = MarkerCluster().add_to(mmm)

# функция, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster

def create_clusters(row):
    Marker(
        [row['lat'], row['lng']],
        popup=f"{row['name']} {row['rating']}",
    ).add_to(marker_cluster)
    
# применяем функцию create_clusters() к каждой строке датафрейма

coffee_data.apply(create_clusters, axis=1)

# выводим карту
mmm
Out[58]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

Всего 1401 заведение в категории кофейня. Большая часть заведений располагается в Центральном районе. Меньше всего заведений - в восточной и южной частях города.

Время работы¶

In [59]:
coffee_24_7 = coffee_data[coffee_data['is_24/7'] == True]
print('Количество круглосуточных кофеен:', len(coffee_24_7))
Количество круглосуточных кофеен: 59
In [64]:
# создаём карту Москвы

mmmm = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# создаём пустой кластер, добавляем его на карту

marker_cluster = MarkerCluster().add_to(mmmm)

# пишем функцию, которая принимает строку датафрейма,
# создаёт маркер в текущей точке и добавляет его в кластер marker_cluster

def create_clusters(row):
    Marker(
        [row['lat'], row['lng']],
        popup=f"{row['name']} {row['rating']}",
    ).add_to(marker_cluster)
    
# применяем функцию create_clusters() к каждой строке датафрейма

coffee_24_7.apply(create_clusters, axis=1)

# выводим карту
mmmm
Out[64]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

Количество круглосуточных кофеен - 59. По карте видно, что большинство находится в центре города. Это логично, т. к. ночью люди любят гулять по центру и спрос на кофе присутствует. В других районах это не распространено.

Рейтинги кофеен¶

In [63]:
# подсчитаем рейтинг кофеен

coffee_rating = coffee_data.groupby('district', as_index=False)['rating'].agg('mean').round(2).sort_values('rating', ascending=False)
coffee_rating
Out[63]:
district rating
5 Центральный административный округ 4.34
4 Северо-Западный административный округ 4.33
2 Северный административный округ 4.29
0 Восточный административный округ 4.28
7 Юго-Западный административный округ 4.28
6 Юго-Восточный административный округ 4.23
8 Южный административный округ 4.23
3 Северо-Восточный административный округ 4.22
1 Западный административный округ 4.19
In [65]:
# создаём карту

mmmmm = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# создаём хороплет с помощью конструктора Choropleth и добавляем его на карт

Choropleth(
    geo_data=state_geo,
    data=coffee_rating,
    columns=['district', 'rating'],
    key_on='feature.name',
    legend_name='Средний рейтинг заведений по районам',
).add_to(mmmmm)

# выводим карту
mmmmm
Out[65]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

Самый высокий рейтинг в округах: Центральный административный округ и Северо-Западный административный округ. Самый низкий рейтинг у Западного административного округа

Стоимость чашки капучино¶

In [66]:
# рассчитаем стоимость чашки каппучино

cup_of_coffee = coffee_data.groupby('district', as_index=False)['middle_coffee_cup'].agg('mean').round(2).sort_values('middle_coffee_cup', 
                                                                                                                      ascending=False)
display(cup_of_coffee)
coffee_price = round(cup_of_coffee['middle_coffee_cup'].mean(), 2)
print(f'Средняя стоимость чашки кофе в Москве:', coffee_price)
district middle_coffee_cup
1 Западный административный округ 188.56
5 Центральный административный округ 187.52
7 Юго-Западный административный округ 184.18
0 Восточный административный округ 174.02
2 Северный административный округ 166.34
4 Северо-Западный административный округ 165.52
3 Северо-Восточный административный округ 165.33
8 Южный административный округ 158.49
6 Юго-Восточный административный округ 151.09
Средняя стоимость чашки кофе в Москве: 171.23
In [67]:
m_6 = Map(location=[moscow_lat, moscow_lng], zoom_start=10)

# создаём хороплет с помощью конструктора Choropleth и добавляем его на карту

Choropleth(
    geo_data=state_geo,
    data=cup_of_coffee,
    columns=['district', 'middle_coffee_cup'],
    key_on='feature.name',
    legend_name='Средняя цена чашки кофе по районам',
).add_to(m_6)

# выводим карту
m_6
Out[67]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Вывод:

Самый дорогой кофе в Западном (188.56) и Центральном округах (187.52). Средняя стоимость чашки кофе для Москвы - 171.23.

Рекомендации:

При открытии кофейни стоит рассмотреть следующие районы: Центральный, Западный и Юго-Западный, т. к. в этих районах высокая стоимость чашки кофе. Это позволит немного снизить цену на старте продаж без существенных потерь для привлечения клиентов. Если рассматривать открытие в Центральном районе, то лучше ориентироваться на круглосуточный режим работы, т.к. это самый оживленный район в ночное время. На Западном и Юго-Западном направлении круглосуточных точек мало.

Возможно, стоит попробовать поэкспериментировать с круглосуточным режимом работы и посмотреть будут ли в ночное время посетители. Если посетители будут, то это будет большим плюсом для бизнеса, т. к. в этих районах конкурентов в ночное время мало. Также можно отметить, что Западный район привлекателен для открытия тем, что в нем самый низкий средний рейтинг у кофеин, что может стать преимуществом при открытии нового заведения и при успешном старте можно стать самым популярным заведением в данном районе.

Как мне кажется, открытие заведений с хорошим рейтингом из Центрального района в других районах (по франшизе) - также хороший вариант, т. к. в Центральном районе очень большая проходимость и количество отзывов и рейтинг Центрального района может хорошо повлиять на заведение и в других районах.

Презентации исследования¶

Презентация: Отчет исследования рынка общественного питания города Москва